package cn.hy.shortUrl.service;

import cn.hutool.bloomfilter.BitMapBloomFilter;
import cn.hutool.bloomfilter.BloomFilterUtil;
import cn.hy.shortUrl.entity.UrlMap;
import cn.hy.shortUrl.manager.UrlMapManager;
import cn.hy.shortUrl.mapper.UrlMapMapper;
import cn.hy.shortUrl.util.HashUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Service
public class ShortLinkV1ServiceImpl implements ShortLinkService {
    private UrlMapMapper urlMapMapper;
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private UrlMapManager urlMapManager;
    private final static String RANDOM_STR = RandomStringUtils.randomAlphanumeric(3);
    private static final BitMapBloomFilter FILTER = BloomFilterUtil.createBitMap(10);
    private static final int TIMEOUT = 30;
    public ShortLinkV1ServiceImpl(UrlMapMapper urlMapMapper, StringRedisTemplate redisTemplate) {
        this.urlMapMapper = urlMapMapper;
        this.stringRedisTemplate = redisTemplate;
    }

    /**
     * 1. 如何判断是否发生了哈希冲突呢？
     * 唯一索引 + 布隆过滤器
     * 2.如何解决哈希冲突呢？
     * 解决办法其实也很简单。如果发生哈希冲突，我们就在长链后拼接一个随机字符串。如果拼接了随机字符串还是发生哈希冲突那就再拼接一个随机字符串。
     */
    @Override
    public String save(String url) {
        String originUrl = url;
        String key = HashUtils.hashToBase62(url);
        boolean contains = FILTER.contains(url);
        if (contains) {
            // 1. 从缓存和数据库查询
            String r = getRedisOrDb(key);
            if (StringUtils.isNotEmpty(r)) {
                return r;
            }
            // 布隆过滤器误判，重新hash
            key = HashUtils.hashToBase62(url + RANDOM_STR);
        }
        // 不存在
        while (true) {
            try {
                UrlMap record = UrlMap.builder().sortUrl(key).longUrl(originUrl).views(0).build();
                int insert = urlMapMapper.insert(record);
                if (insert > 0) {
                    FILTER.add(originUrl);
                    stringRedisTemplate.opsForValue().set(key, url, TIMEOUT, TimeUnit.MINUTES);
                }
                break;
            } catch (DuplicateKeyException e) {
                //数据库已经存在此短链接，则可能是布隆过滤器误判，在长链接后加上指定字符串，重新hash
                key = HashUtils.hashToBase62(url + RANDOM_STR);
            }
        }
        return key;
    }

    private String getRedisOrDb(String key) {
        String r = stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isEmpty(r)) {
            return lookup(r);
        }
        return r;
    }

    @Override
    public String lookup(String key) {
        String longURL = stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isNotEmpty(longURL)) {
            stringRedisTemplate.expire(key, TIMEOUT, TimeUnit.MINUTES); // 续命
            return longURL;
        }
        String longUrl = "";
        if (StringUtils.isNotEmpty((longUrl = urlMapManager.getLongUrlByKey(key)))) {
            stringRedisTemplate.opsForValue().set(key, longUrl, TIMEOUT, TimeUnit.MINUTES);
        }
        return longUrl;
    }


}
